<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JSON String Extractor</title>
  <style>
* {
  box-sizing: border-box;
}

body {
  font-family: Helvetica, Arial, sans-serif;
  margin: 0;
  padding: 20px;
  background: #f5f5f5;
}

.container {
  max-width: 1200px;
  margin: 0 auto;
  background: white;
  padding: 30px;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

h1 {
  margin: 0 0 20px 0;
  font-size: 24px;
  font-weight: normal;
}

.input-section {
  margin-bottom: 30px;
}

#jsonInput {
  width: 100%;
  min-height: 150px;
  padding: 12px;
  border: 2px solid #ddd;
  border-radius: 4px;
  font-family: 'Courier New', monospace;
  font-size: 16px;
  resize: vertical;
}

#jsonInput:focus {
  outline: none;
  border-color: #4a90e2;
}

.error {
  color: #e74c3c;
  padding: 12px;
  background: #fef5f5;
  border-radius: 4px;
  margin-top: 10px;
  display: none;
}

.results {
  margin-top: 30px;
}

.string-item {
  margin-bottom: 20px;
  padding: 15px;
  background: #fafafa;
  border-radius: 4px;
  border: 1px solid #e0e0e0;
}

.string-path {
  font-size: 12px;
  color: #666;
  margin-bottom: 8px;
  font-family: 'Courier New', monospace;
}

.string-content {
  position: relative;
}

.string-textarea {
  width: 100%;
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-family: 'Courier New', monospace;
  font-size: 16px;
  resize: none;
  background: white;
  overflow-y: hidden;
}

.copy-button {
  margin-top: 8px;
  padding: 8px 16px;
  background: #4a90e2;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  font-family: Helvetica, Arial, sans-serif;
}

.copy-button:hover {
  background: #357abd;
}

.copy-button:active {
  background: #2868a8;
}

.copy-button.copied {
  background: #27ae60;
}

.load-example-button {
  padding: 6px 12px;
  background: #666;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 13px;
  font-family: Helvetica, Arial, sans-serif;
}

.load-example-button:hover {
  background: #555;
}

.load-example-button:active {
  background: #444;
}

.load-example-button:disabled {
  background: #ccc;
  cursor: not-allowed;
}

.count {
  font-size: 14px;
  color: #666;
  margin-bottom: 15px;
}
  </style>
</head>
<body>
  <div class="container">
    <h1>JSON string extractor</h1>
    <p style="margin: 0 0 20px 0; font-size: 14px; color: #666; line-height: 1.2;">Paste JSON below to extract all strings that are longer than 20 characters or contain newlines.</p>
    
    <div class="input-section">
      <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
        <label for="jsonInput" style="font-size: 14px; color: #666;">Paste JSON here:</label>
        <div style="display: flex; gap: 8px;">
          <button id="loadGist" class="load-example-button">Load Gist</button>
          <a href="#gist=1d1013ba059af76461153722005a039d" class="load-example-button" style="text-decoration: none; display: inline-block; line-height: 1.5;">Load example JSON</a>
        </div>
      </div>
      <textarea id="jsonInput" placeholder="Paste your JSON here..."></textarea>
      <div id="error" class="error"></div>
    </div>

    <div id="results" class="results"></div>
  </div>

  <script type="module">
const jsonInput = document.getElementById('jsonInput');
const errorDiv = document.getElementById('error');
const resultsDiv = document.getElementById('results');
const loadGistButton = document.getElementById('loadGist');

function extractGistId(input) {
  // Try to extract Gist ID from URL or use input directly
  const urlMatch = input.match(/gist\.github\.com\/[^\/]+\/([a-fA-F0-9]+)/i);
  if (urlMatch) {
    return urlMatch[1];
  }
  // Check if it's just a Gist ID (alphanumeric string)
  if (/^[a-fA-F0-9]+$/i.test(input.trim())) {
    return input.trim();
  }
  return null;
}

async function loadGistJSON(gistId) {
  const response = await fetch(`https://api.github.com/gists/${gistId}`);
  
  if (!response.ok) {
    throw new Error(`Failed to fetch Gist: ${response.status} ${response.statusText}`);
  }
  
  const gistData = await response.json();
  
  // Find the first file that contains valid JSON
  for (const filename in gistData.files) {
    const file = gistData.files[filename];
    try {
      // Try to parse the content as JSON
      JSON.parse(file.content);
      return file.content;
    } catch (e) {
      // Not valid JSON, continue to next file
      continue;
    }
  }
  
  throw new Error('No valid JSON file found in the Gist');
}

async function loadGist() {
  const input = prompt('Enter a Gist URL or Gist ID:');
  
  if (!input) {
    return; // User cancelled
  }
  
  const gistId = extractGistId(input);
  
  if (!gistId) {
    showError('Invalid Gist URL or ID. Please provide a valid GitHub Gist URL or ID.');
    return;
  }
  
  loadGistButton.disabled = true;
  loadGistButton.textContent = 'Loading...';
  
  try {
    const jsonContent = await loadGistJSON(gistId);
    jsonInput.value = jsonContent;
    processJSON();
    
    // Update the URL hash
    window.location.hash = `gist=${gistId}`;
    
    loadGistButton.textContent = 'Load Gist';
  } catch (e) {
    showError(`Failed to load Gist: ${e.message}`);
    loadGistButton.textContent = 'Load Gist';
  } finally {
    loadGistButton.disabled = false;
  }
}

async function loadGistFromHash() {
  const hash = window.location.hash;
  const gistMatch = hash.match(/gist=([a-fA-F0-9]+)/i);
  
  if (gistMatch) {
    const gistId = gistMatch[1];
    try {
      const jsonContent = await loadGistJSON(gistId);
      jsonInput.value = jsonContent;
      processJSON();
    } catch (e) {
      showError(`Failed to load Gist from URL: ${e.message}`);
    }
  }
}

loadGistButton.addEventListener('click', loadGist);

function showError(message) {
  errorDiv.textContent = message;
  errorDiv.style.display = 'block';
}

function hideError() {
  errorDiv.style.display = 'none';
}

function shouldDisplay(str) {
  return str.includes('\n') || str.length > 20;
}

function extractStrings(obj, path = '') {
  const results = [];
  
  if (typeof obj === 'string') {
    if (shouldDisplay(obj)) {
      results.push({ path, value: obj });
    }
  } else if (Array.isArray(obj)) {
    obj.forEach((item, index) => {
      const newPath = path ? `${path}[${index}]` : `[${index}]`;
      results.push(...extractStrings(item, newPath));
    });
  } else if (obj !== null && typeof obj === 'object') {
    Object.keys(obj).forEach(key => {
      const newPath = path ? `${path}.${key}` : key;
      results.push(...extractStrings(obj[key], newPath));
    });
  }
  
  return results;
}

function resizeTextarea(textarea) {
  // Reset height to auto to get the correct scrollHeight
  textarea.style.height = 'auto';
  // Set height to scrollHeight to fit content
  textarea.style.height = textarea.scrollHeight + 'px';
}

function resizeAllTextareas() {
  document.querySelectorAll('.string-textarea').forEach(textarea => {
    resizeTextarea(textarea);
  });
}

function copyToClipboard(text, button) {
  navigator.clipboard.writeText(text).then(() => {
    const originalText = button.textContent;
    button.textContent = 'Copied!';
    button.classList.add('copied');
    
    setTimeout(() => {
      button.textContent = originalText;
      button.classList.remove('copied');
    }, 2000);
  }).catch(err => {
    console.error('Failed to copy:', err);
    alert('Failed to copy to clipboard');
  });
}

function renderResults(strings) {
  if (strings.length === 0) {
    resultsDiv.innerHTML = '<p class="count">No strings found that are longer than 20 characters or contain newlines.</p>';
    return;
  }
  
  let html = `<p class="count">Found ${strings.length} string${strings.length === 1 ? '' : 's'}:</p>`;
  
  strings.forEach((item, index) => {
    const escapedValue = item.value
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;');
    
    html += `
      <div class="string-item">
        <div class="string-path">${item.path || '(root)'}</div>
        <div class="string-content">
          <textarea class="string-textarea" data-index="${index}">${escapedValue}</textarea>
          <button class="copy-button" data-index="${index}">Copy to clipboard</button>
        </div>
      </div>
    `;
  });
  
  resultsDiv.innerHTML = html;
  
  // Add event listeners to copy buttons
  document.querySelectorAll('.copy-button').forEach(button => {
    button.addEventListener('click', () => {
      const index = button.getAttribute('data-index');
      const textarea = document.querySelector(`.string-textarea[data-index="${index}"]`);
      copyToClipboard(textarea.value, button);
    });
  });
  
  // Resize all textareas after rendering
  requestAnimationFrame(() => {
    resizeAllTextareas();
  });
}

function processJSON() {
  const input = jsonInput.value.trim();
  
  if (!input) {
    resultsDiv.innerHTML = '';
    hideError();
    return;
  }
  
  try {
    const parsed = JSON.parse(input);
    hideError();
    
    const strings = extractStrings(parsed);
    renderResults(strings);
  } catch (e) {
    showError(`Invalid JSON: ${e.message}`);
    resultsDiv.innerHTML = '';
  }
}

jsonInput.addEventListener('input', processJSON);

// Resize textareas when window is resized
let resizeTimeout;
window.addEventListener('resize', () => {
  clearTimeout(resizeTimeout);
  resizeTimeout = setTimeout(() => {
    resizeAllTextareas();
  }, 100);
});

// Load Gist from hash if present, otherwise process any existing content
loadGistFromHash().then(() => {
  processJSON();
});

// Listen for hash changes to load gists dynamically
window.addEventListener('hashchange', () => {
  loadGistFromHash();
});
  </script>
</body>
</html>
